﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;

namespace Mindfor.Web.Data
{
	/// <summary>
	/// Class for module option data object.
	/// </summary>
	public class ModuleOption : IdEntity
	{
		/// <summary>
		/// Gets or sets option name.
		/// </summary>
		[DisplayName("Название")]
		public virtual string OptionName { get; set; }

		/// <summary>
		/// Gets or sets module name.
		/// </summary>
		public virtual string ModuleName { get; set; }

		/// <summary>
		/// Gets or sets description for option.
		/// </summary>
		[DisplayName("Описание")]
		public virtual string Description { get; set; }

		/// <summary>
		/// Gets or sets option value type.
		/// </summary>
		[DisplayName("Тип значения")]
		public virtual ModuleOptionType ValueType { get; set; }

		/// <summary>
		/// Gets or sets value stored as string.
		/// </summary>
		protected virtual string ValueStr { get; set; }

		/// <summary>
		/// Gets or sets value stored as number.
		/// </summary>
		protected virtual double? ValueNum { get; set; }

		/// <summary>
		/// Initializes new instance.
		/// </summary>
		public ModuleOption()
		{
		}

		/// <summary>
		/// Initializes new instance.
		/// </summary>
		/// <param name="optionName">Option name.</param>
		/// <param name="valueType">Value type.</param>
		/// <param name="value">Option default value.</param>
		/// <param name="description">Description text.</param>
		public ModuleOption(string optionName, ModuleOptionType valueType, object value, string description)
		{
			OptionName = optionName;
			ValueType = valueType;
			Description = description;
			SetValue(value);
		}

		/// <summary>
		/// Returns option value.
		/// </summary>
		/// <returns>Option value or null of value not set.</returns>
		public virtual object GetValue()
		{
			// get value
			switch (this.ValueType)
			{
				case ModuleOptionType.Integer:
				case ModuleOptionType.Double:
					return ValueNum;

				case ModuleOptionType.Boolean:
					return ValueNum == null ? false : (ValueNum == 1);

				case ModuleOptionType.DateTime:
					if (this.ValueNum != null)
					{
						long ticks = ValueNum.To<long>();
						return new DateTime(ticks);
					}
					break;

				case ModuleOptionType.String:
					return ValueStr == null ? null : ValueStr.ToString();
			}

			return null;
		}

		/// <summary>
		/// Returns option value converted to specified type.
		/// </summary>
		/// <typeparam name="T">Type to convert option to.</typeparam>
		/// <param name="defaultValue">Default value to return if passes type and option type does not match.</param>
		/// <returns>Option value.</returns>
		public virtual T GetValue<T>(T defaultValue = default(T))
		{
			// check types match
			Type t = typeof(T);
			if (!this.CheckTypeMatch(t))
				throw new InvalidCastException(String.Format("Type \"{0}\" is invalid for option \"{1}\".", t.FullName, this.OptionName));

			// get value
			return GetValue().To<T>(defaultValue);
		}

		/// <summary>
		/// Sets option value.
		/// </summary>
		/// <param name="value">New option value.</param>
		/// <returns>True, if value is set. Otherwise false.</returns>
		public virtual bool SetValue(object value)
		{
			// set value
			ValueNum = null;
			ValueStr = null;
			if (value != null)
			{
				try
				{
					switch (ValueType)
					{
						case ModuleOptionType.Integer:
							ValueNum = value.ToOrException<int>();
							break;
						case ModuleOptionType.Double:
							ValueNum = value.ToOrException<double>(); ;
							break;
						case ModuleOptionType.Boolean:
							ValueNum = value.ToOrException<bool>() ? 1 : 0;
							break;
						case ModuleOptionType.DateTime:
							ValueNum = value.ToOrException<DateTime>().Ticks;
							break;
						case ModuleOptionType.String:
							ValueStr = value.ToOrException<string>();
							break;
					}
				}
				catch
				{
					return false;
				}
			}

			// if here => everything is ok
			return true;
		}

		/// <summary>
		/// Checks if type matches option value type.
		/// </summary>
		/// <param name="typeToCheck">Type to check.</param>
		/// <returns>True, if type matches. Otherwise false.</returns>
		public virtual bool CheckTypeMatch(Type typeToCheck)
		{
			switch (ValueType)
			{
				case ModuleOptionType.Integer:
					return typeToCheck == typeof(int) || typeToCheck == typeof(Int16) || typeToCheck == typeof(Int64);
				case ModuleOptionType.Double:
					return typeToCheck == typeof(double) || typeToCheck == typeof(float) || typeToCheck == typeof(decimal)
						|| typeToCheck == typeof(int) || typeToCheck == typeof(Int16) || typeToCheck == typeof(Int64);
				case ModuleOptionType.Boolean:
					return typeToCheck == typeof(bool);
				case ModuleOptionType.DateTime:
					return typeToCheck == typeof(DateTime);
				case ModuleOptionType.String:
					return typeToCheck == typeof(string);
			}

			return false;
		}

		/// <summary>
		/// Returns option value type in friendly string.
		/// </summary>
		public virtual string GetValueTypeFriendlyName()
		{
			switch (ValueType)
			{
				case ModuleOptionType.Integer:
					return "Целое";
				case ModuleOptionType.Double:
					return "Дробное";
				case ModuleOptionType.Boolean:
					return "Булево";
				case ModuleOptionType.DateTime:
					return "Дата/время";
			}

			return "Строка";
		}
	}
}
